package org.abettor.angleworm.media.rtp;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.media.protocol.DataSource;
import javax.media.rtp.RTPManager;
import javax.media.rtp.SendStream;
import javax.media.rtp.SessionAddress;

import org.abettor.angleworm.media.MediaTransmitter;

/**
 * <p>通过RTP/UDP协议传输媒体流的媒体传输类</p>
 * <p>支持的目标URL格式为：<i>rtp://&lt;Dest IP address | Dest host name&gt;:&lt;Dest port&gt;</i></p>
 * @author shawn
 *
 */
public class RtpTransmitter implements MediaTransmitter
{
	private static final int BIND_PORT_BASE = 1026;
	private static final int BIND_PORT_SPACE = (65534 - BIND_PORT_BASE) / 2;

	private InetAddress localAddr;
	private InetAddress destAddr;
	private int destPort;
	private Map<DataSource, RTPManager> rtpMgrMap = new HashMap<DataSource, RTPManager>();
	private Map<DataSource, SendStream> dsMap = new HashMap<DataSource, SendStream>();

	/**
	 * 默认构造函数
	 * @param targetUrl
	 */
	public RtpTransmitter(String targetUrl)
	{
		//	生成目标SessionAddress
		int seperatorIdx = targetUrl.lastIndexOf(":");
		String host = targetUrl.substring("rtp://".length(), seperatorIdx);
		String port = targetUrl.substring(seperatorIdx + 1);
		destPort = Integer.parseInt(port);
		try
		{
			destAddr = InetAddress.getByName(host);
		}
		catch (UnknownHostException e)
		{
			e.printStackTrace();
		}

		//	获取本机地址
		try
		{
			localAddr = InetAddress.getLocalHost();
		}
		catch (UnknownHostException e)
		{
			e.printStackTrace();
			return;
		}
	}

	/**
	 * @see org.abettor.angleworm.media.MediaTransmitter#appendDataSource(DataSource)
	 */
	@Override
	public void appendDataSource(DataSource ds) throws IOException
	{
		int localPort = getRandomDatagramPort(localAddr);
		if(localPort == 0)
		{
			throw new IOException("No DatagramSocket port can be bound.");
		}

		System.err.println("RTP connection binds local port " + localPort + ".");
		SessionAddress saLocal = new SessionAddress(localAddr, localPort);
		SessionAddress saDest = new SessionAddress(destAddr, destPort);
		RTPManager rtpMgr = RTPManager.newInstance();
		SendStream ss = null;
		try
		{
			rtpMgr.initialize(saLocal);
			rtpMgr.addTarget(saDest);
			ss = rtpMgr.createSendStream(ds, 0);
			ss.start();
		}
		catch (Exception e)
		{
			if(ss != null)
			{
				ss.close();
			}
			rtpMgr.dispose();
			rtpMgr = null;
			throw new IOException(e);
		}
		dsMap.put(ds, ss);
	}

	/**
	 * @see org.abettor.angleworm.media.MediaTransmitter#removeDataSource(DataSource)
	 */
	@Override
	public void removeDataSource(DataSource ds) throws IOException
	{
		//	清理SendStream
		SendStream ss = dsMap.get(ds);
		if(ss != null)
		{
			ss.stop();
			ss.close();
			dsMap.remove(ds);
		}

		//	清理RTPManager
		RTPManager rtpMgr = rtpMgrMap.get(ds);
		if(rtpMgr != null)
		{
			rtpMgr.dispose();
			rtpMgrMap.remove(ds);
		}
	}

	/**
	 * @see org.abettor.angleworm.media.MediaTransmitter#removeAllDataSource()
	 */
	@Override
	public void removeAllDataSource() throws IOException
	{
		//	清理SendStream
		for(SendStream ss : dsMap.values())
		{
			ss.stop();
			ss.close();
		}
		dsMap.clear();

		//	清理RTPManager
		for(RTPManager rtpMgr : rtpMgrMap.values())
		{
			rtpMgr.dispose();
		}
		rtpMgrMap.clear();
	}

	/**
	 * 设定绑定地址
	 * @param bindAddr
	 */
	public void setBindAddress(String bindAddr)
	{
		try
		{
			localAddr = InetAddress.getByName(bindAddr);
		}
		catch(UnknownHostException e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * 获取绑定地址
	 */
	public String getBindAddress()
	{
		return localAddr.getHostAddress();
	}

	//	随机选取可用的RTP端口号
	private int getRandomDatagramPort(InetAddress localAddr) throws IOException
	{
		Random rand = new Random();
		rand.setSeed(System.currentTimeMillis());
		for(int i = 0; i < 5; i ++)
		{
			int num = Math.abs(rand.nextInt());
			num %= BIND_PORT_SPACE;
			int port = num * 2 + BIND_PORT_BASE;
			try
			{
				DatagramSocket ds1 = new DatagramSocket(port, localAddr);
				ds1.close();
				DatagramSocket ds2 = new DatagramSocket(port + 1, localAddr);
				ds2.close();
			}
			catch(SocketException e)
			{
				continue;
			}

			return port;
		}

		return 0;
	}
}
